package com.hk.core.autoconfigure.exception;

import com.hk.commons.util.JsonUtils;
import com.hk.commons.util.Lazy;
import com.hk.commons.util.SpringContextHolder;
import com.hk.commons.util.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Map;


/**
 * 使用数据库记录异常信息
 *
 * @author Kevin
 */
@Slf4j
@RequiredArgsConstructor
public class DataSourceUncaughtExceptionConsumer implements UncaughtExceptionConsumer, InitializingBean {

    /**
     * 默认创建表语法
     */
    private static final String CREATE_TABLE_GRAMMAR = """
            CREATE TABLE IF NOT EXISTS ${table_name} (
                id                      bigint NOT NULL,
                user_id                 bigint DEFAULT NULL,
                account                 varchar(50) DEFAULT NULL,
                exception_class_name    varchar(100) NOT NULL,
                request_time            datetime NOT NULL,
                ip                      varchar(50) DEFAULT NULL,
                uri                     varchar(50) DEFAULT NULL,
                request_method          varchar(10) DEFAULT NULL,
                parameters              varchar(200),
                message                 varchar(500) NOT NULL,
                body                    text,
                async                   tinyint(4) NOT NULL,
                invoke_method_name      varchar(100),
                invoke_method_args      text,
                PRIMARY KEY (id) USING BTREE
            ) ENGINE=InnoDB\
            """;

    private static final String DEFAULT_TABLE_NAME = "t_exception";

    /**
     * 防止一个应用，连接多个数据库
     */
    @Setter
    private String dataSourceBeanName;

    private final Lazy<DataSource> dataSource = Lazy.of(() -> StringUtils.hasLength(dataSourceBeanName) ?
            SpringContextHolder.getBean(this.dataSourceBeanName, DataSource.class) :
            SpringContextHolder.getBean(DataSource.class));

    @Setter
    private boolean autoCreateTableIfNotExist = true;

    @Setter
    private String tableName;

    @Setter
    private String insertSql;

    private String getInsertSql() {
        if (null == insertSql) {
            insertSql = String.format(""" 
                    INSERT INTO %s (id,user_id,account,exception_class_name,request_time,ip,uri,request_method,parameters,message,body,async,invoke_method_name,invoke_method_args)\s\
                    VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)\
                    """, StringUtils.defaultIfEmpty(tableName, DEFAULT_TABLE_NAME));
        }
        return insertSql;
    }

    @Override
    public void accept(ExceptionDetails exceptionDetails, Throwable t) {
        final var insertSql = getInsertSql();
        try (var connection = dataSource.get().getConnection();
             var ps = connection.prepareStatement(insertSql)) {
            ps.setLong(1, exceptionDetails.getId());
            ps.setLong(2, exceptionDetails.getUserId());
            ps.setString(3, exceptionDetails.getAccount());
            ps.setString(4, exceptionDetails.getExceptionClassName());
            ps.setTimestamp(5, Timestamp.valueOf(exceptionDetails.getRequestTime()));
            ps.setString(6, exceptionDetails.getIp());
            ps.setString(7, exceptionDetails.getUri());
            ps.setString(8, exceptionDetails.getMethod());
            ps.setString(9, JsonUtils.serialize(exceptionDetails.getParameters()));
            ps.setString(10, exceptionDetails.getMessage());
            ps.setString(11, exceptionDetails.getBody());
            ps.setBoolean(12, exceptionDetails.getAsync());
            ps.setString(13, exceptionDetails.getInvokeMethodName());
            ps.setString(14, exceptionDetails.getInvokeMethodArgs());
            var rows = ps.executeUpdate();
            if (log.isDebugEnabled()) {
                log.debug("Execute insert sql:[{}] , result :{}", insertSql, rows);
            }
        } catch (SQLException e) {
            // ignore
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void afterPropertiesSet() {
        if (autoCreateTableIfNotExist) {
            var createTableSchema = StringUtils.processTemplate(CREATE_TABLE_GRAMMAR, Map.of("table_name", tableName));
            try (var connection = dataSource.get().getConnection();
                 var ps = connection.prepareStatement(createTableSchema)) {
                var rows = ps.executeUpdate();
                if (log.isDebugEnabled()) {
                    log.debug("Execute create Table Schema:{},result:{}", createTableSchema, rows > 0);
                }
            } catch (SQLException e) {
                log.warn(e.getMessage(), e);
            }
        }

    }
}
